package com.nforce.linremote.serverapi;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang3.StringUtils;

import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.nforce.linremote.converters.AESUtils;
import com.nforce.linremote.model.LFunction;
import com.nforce.linremote.model.LibraryCall;
import com.nforce.linremote.model.Packet;
import com.nforce.linremote.model.Script;
import com.nforce.linremote.model.Server;
import com.nforce.linremote.networking.LRSocket;
import com.nforce.linremote.networking.SessionUtils;
import com.nforce.linremote.scripting.LRMLParser;

public class Library {

    public enum Api {
	CONNECT("connect", false),
	GETHOSTNAME("getHostName", false),
	DISCONNECT("disconnect", true),
	GETLIST("getList", true),
	ISSESSIONACTIVE("isSessionActive", true),
	EXECUTE("execute", true);

	String functionName;
	boolean encrypted;

	Api(String name, boolean encrypted) {
	    this.functionName = name;
	    this.encrypted = encrypted;
	}

	@Override
	public String toString() {
	    return functionName;
	}
    }

    private static String parseJson(String json, String format, String... args) {
	JsonElement root = new JsonParser().parse(json);
	List<String> results = new ArrayList<String>();
	for (String a : args) {
	    results.add(root.getAsJsonObject().get(a).getAsString());
	}
	if (format == null) {
	    String result = "";
	    for (String r : results) {
		result += r;
	    }
	    return result;
	} else {
	    String result = String.format(format, results.toArray());
	    return result;
	}
    }

    private static byte[] encrypt(String json, Api function) {
	if (!function.encrypted) {
	    try {
		return json.getBytes("UTF8");
	    } catch (UnsupportedEncodingException e) {
		return json.getBytes();
	    }
	}
	byte[] result = null;
	try {
	    result = AESUtils.encrypt(SessionUtils.getSid(), json);
	} catch (Exception e) {
	    e.printStackTrace();
	}
	return result;
    }

    private static String decrypt(byte[] data, Api function) {
	if (!function.encrypted) {
	    try {
		return new String(data, "UTF8").trim();
	    } catch (UnsupportedEncodingException e) {
		return new String(data).trim();
	    }
	}
	String result = "";
	try {
	    result = AESUtils.decrypt(SessionUtils.getSid(), data);
	} catch (Exception e) {
	    e.printStackTrace();
	}
	return result.trim();
    }

    public static Server getServerInfo(String ip, int port) {
	LRSocket socket = new LRSocket(ip, port);
	LibraryCall call = new LibraryCall(Api.GETHOSTNAME.functionName, null);
	Gson gson = new Gson();
	String jsonCall = gson.toJson(call);
	Packet packet = socket.recieve(encrypt(jsonCall, Api.GETHOSTNAME));
	if (packet == null) {
	    return null;
	}
	Server server = new Server();
	server.setAddress(packet.getIpAddress());
	server.setPort(packet.getPort());
	server.setHostname(parseJson(decrypt(packet.getData(), Api.GETHOSTNAME), null, "hostname"));
	return server;
    }

    public static String connect(Server server) {
	if (SessionUtils.isSessionActive()) {
	    SessionUtils.terminateSession();
	}
	LRSocket socket = new LRSocket(server.getAddress(), server.getPort());
	LibraryCall call = new LibraryCall(Api.CONNECT.functionName, null);
	Gson gson = new Gson();
	String jsonCall = gson.toJson(call);
	Packet p = socket.recieve(encrypt(jsonCall, Api.CONNECT));
	if (p == null) {
	    return null;
	}
	String sid = parseJson(decrypt(p.getData(), Api.CONNECT), null, "sessionID");

	return StringUtils.isEmpty(sid) ? null : sid;
    }

    public static void disconnect(Server server) {
	LRSocket socket = new LRSocket(server.getAddress(), server.getPort());
	LibraryCall call = new LibraryCall(Api.DISCONNECT.functionName, SessionUtils.getSid());
	Gson gson = new Gson();
	String jsonCall = gson.toJson(call);
	socket.send(encrypt(jsonCall, Api.DISCONNECT));
    }

    public static List<String> getList() {
	Server server = SessionUtils.getServer();
	LRSocket socket = new LRSocket(server.getAddress(), server.getPort());
	LibraryCall call = new LibraryCall(Api.GETLIST.functionName, SessionUtils.getSid());
	Gson gson = new Gson();
	String jsonCall = gson.toJson(call);
	Packet packet = socket.recieve(encrypt(jsonCall, Api.GETLIST));
	if (packet == null) {
	    return null;
	}

	ListResponse parsedJson = gson.fromJson(decrypt(packet.getData(), Api.GETLIST), ListResponse.class);
	List<String> resultValues = new ArrayList<String>();
	for (String s : parsedJson.interfaces) {
	    resultValues.add(s);
	}
	return resultValues;
    }

    private class ListResponse {
	public List<String> interfaces;
    }

    public static boolean isSessionActive(Server server, String sid) {
	LRSocket socket = new LRSocket(server.getAddress(), server.getPort());
	LibraryCall call = new LibraryCall(Api.ISSESSIONACTIVE.functionName, sid);
	Gson gson = new Gson();
	String jsonCall = gson.toJson(call);
	Packet packet = socket.recieve(encrypt(jsonCall, Api.ISSESSIONACTIVE));
	if (packet == null) {
	    return false;
	}
	String active = parseJson(decrypt(packet.getData(), Api.ISSESSIONACTIVE), null, "active");
	return Boolean.parseBoolean(active);
    }

    public static void execute(String command, List<Object> args, Script script) {
	command = LRMLParser.parseFunctions(command, script);
	for (int i = 0; i < args.size(); i++) {
	    if (args.get(i) instanceof String) {
		args.set(i, LRMLParser.parseFunctions((String) args.get(i), script));
	    }
	}
	Server server = SessionUtils.getServer();
	LRSocket socket = new LRSocket(server.getAddress(), server.getPort());
	LibraryCall call = new LibraryCall(Api.EXECUTE.functionName, SessionUtils.getSid(), command, args);

	Gson gson = new Gson();
	String jsonCall = gson.toJson(call);
	socket.send(encrypt(jsonCall, Api.EXECUTE));
    }

    public static String executeFunction(LFunction function, Script script) {
	String command = LRMLParser.parseFunctions(function.getCommand(), script);
	List<Object> args = new ArrayList<Object>();
	for (int i = 0; i < function.getArgs().size(); i++) {
	    Object arg = function.getArgs().get(i).getArg();
	    if (arg instanceof String) {
		arg = LRMLParser.parseFunctions((String) arg, script);
	    }
	    args.add(arg);
	}
	Server server = SessionUtils.getServer();
	LRSocket socket = new LRSocket(server.getAddress(), server.getPort());
	LibraryCall call = new LibraryCall(Api.EXECUTE.functionName, SessionUtils.getSid(), command, args);

	Gson gson = new Gson();
	String jsonCall = gson.toJson(call);
	Packet p = socket.recieve(encrypt(jsonCall, Api.EXECUTE));
	return parseJson(decrypt(p.getData(), Api.EXECUTE), function.getFormat(), function.getResult().toArray(new String[function.getResult().size()]));
    }

}